#ifndef NSSeqVRPShift20_HPP_
#define NSSeqVRPShift20_HPP_

// Framework includes
#include "../OptFrame/NSSeq.hpp"

// Own includes
#include "ProblemInstance.hpp"
#include "Memory.h"
#include "Solution.h"

using namespace std;

class MoveVRPShift20: public Move<RepPRV, MemPRVP>
{
private:

	int r1, r2; //routes
	int cli;//cli shifted in r1
	int pos;// insertion position in r2

	PRVPProblemInstance& pPRVP;
public:

	using Move<RepPRV, MemPRVP>::apply; // prevents name hiding
	using Move<RepPRV, MemPRVP>::canBeApplied; // prevents name hiding

	MoveVRPShift20(PRVPProblemInstance& _pPRVP, int _r1, int _r2, int _cli, int _pos) :
		pPRVP(_pPRVP), r1(_r1), r2(_r2), cli(_cli), pos(_pos)
	{
	}

	virtual ~MoveVRPShift20()
	{
	}

	int get_r1()
	{
		return r1;
	}

	int get_r2()
	{
		return r2;
	}

	int get_cli()
	{
		return cli;
	}

	int get_pos()
	{
		return pos;
	}

	bool canBeApplied(const RepPRV& rep)
	{
		bool numRoutes = rep.size() >= 2;
		return ((r1 >= 0) && (r2 >= 0) && (cli >= 0) && (pos >= 0) && numRoutes);
	}

	Move<RepPRV, MemPRVP>& apply(RepPRV& rep)
	{
		//pegando o cliente
		int c = rep.at(r1).at(cli);
		int c2 = rep.at(r1).at(cli + 1);

		//removendo os clientes de cada rota
		rep.at(r1).erase(rep.at(r1).begin() + cli);
		rep.at(r1).erase(rep.at(r1).begin() + cli);

		//fazendo a inserção
		rep.at(r2).insert(rep.at(r2).begin() + pos, c2);
		rep.at(r2).insert(rep.at(r2).begin() + pos, c);

		return *new MoveVRPShift20(pPRVP, r2, r1, pos, cli);
	}

	Move<RepPRV, MemPRVP>& apply(MemPRVP& m, RepPRV& rep)
	{
		Matrix<double>& dist = (*pPRVP.dist);

		double cargaCs;
		cargaCs = pPRVP.dados->at(rep.at(r1).at(cli)).at(DEMANDA) + pPRVP.dados->at(rep.at(r1).at(cli + 1)).at(DEMANDA);

		//calculando a carga ocupada nas rotas
		double cargaR1 = 0, cargaR2 = 0;
		//pegando as cargas da memória
		cargaR1 = m.getRouteLoad().at(r1);
		cargaR2 = m.getRouteLoad().at(r2);

		double inf_antes = min(0.0, pPRVP.capacidade - cargaR1) + min(0.0, pPRVP.capacidade - cargaR2);
		double inf_dps = max(0.0, (cargaR1 - cargaCs) - pPRVP.capacidade) + max(0.0, (cargaR2 + cargaCs) - pPRVP.capacidade);

		//calculo do custo do movimento
		double delta = 0;
		double delta_inf = inf_antes + inf_dps;

		//para facilitar as contas, adicionando o depósito no final das rotas
		rep.at(r1).push_back(0);
		rep.at(r2).push_back(0);
		//remocao dos arcos antigos
		if (cli - 1 < 0)//deve considerar o depósito
			delta += -dist(0, rep.at(r1).at(cli)) - dist(rep.at(r1).at(cli + 1), rep.at(r1).at(cli + 2));
		else
			delta += -dist(rep.at(r1).at(cli - 1), rep.at(r1).at(cli)) - dist(rep.at(r1).at(cli + 1), rep.at(r1).at(cli + 2));

		if (pos - 1 < 0)//deve considerar o depósito
			delta += -dist(0, rep.at(r2).at(pos));
		else
			delta += -dist(rep.at(r2).at(pos - 1), rep.at(r2).at(pos));

		//adicionando os novos arcos
		if (cli - 1 < 0)
			delta += dist(0, rep.at(r1).at(cli + 2));
		else
			delta += dist(rep.at(r1).at(cli - 1), rep.at(r1).at(cli + 2));

		if (pos - 1 < 0)
			delta += dist(0, rep.at(r1).at(cli)) + dist(rep.at(r1).at(cli + 1), rep.at(r2).at(pos));
		else
			delta += dist(rep.at(r2).at(pos - 1), rep.at(r1).at(cli)) + dist(rep.at(r1).at(cli + 1), rep.at(r2).at(pos));

		//removendo os depósitos que facilitaram a conta
		rep.at(r1).pop_back();
		rep.at(r2).pop_back();

		//pegando o cliente
		int c = rep.at(r1).at(cli);
		int c2 = rep.at(r1).at(cli + 1);

		//removendo os clientes de cada rota
		rep.at(r1).erase(rep.at(r1).begin() + cli);
		rep.at(r1).erase(rep.at(r1).begin() + cli);

		//fazendo a inserção
		rep.at(r2).insert(rep.at(r2).begin() + pos, c2);
		rep.at(r2).insert(rep.at(r2).begin() + pos, c);

		//setando as variações de fo e inviabilidade
		m.setDelta(delta);
		m.setDelta_inf(delta_inf);

		//atualizando as cargas
		m.getRouteLoad().at(r1) = cargaR1 - cargaCs;
		m.getRouteLoad().at(r2) = cargaR2 + cargaCs;

		return *new MoveVRPShift20(pPRVP, r2, r1, pos, cli);

	}

	virtual bool operator==(const Move<RepPRV, MemPRVP>& _m) const
	{
		const MoveVRPShift20& m = (const MoveVRPShift20&) _m;
		return ((r1 == m.r1) && (r2 == m.r2) && (cli == m.cli) && (pos == m.pos));
	}

	void print() const
	{
		cout << "MoveVRPShift20( ";
		cout << r1 << " , ";
		cout << r2 << " , ";
		cout << cli << " , ";
		cout << pos << " )";
	}
};

class NSIteratorVRPShift20: public NSIterator<RepPRV, MemPRVP>
{

private:

	MoveVRPShift20* m;
	vector<MoveVRPShift20*> moves;
	int index; //index of moves
	const RepPRV& r;
	PRVPProblemInstance& pPRVP;

public:

	NSIteratorVRPShift20(PRVPProblemInstance& _pPRVP, const RepPRV& _r) :
		pPRVP(_pPRVP), r(_r)
	{
		m = NULL;
		index = 0;
	}

	virtual ~NSIteratorVRPShift20()
	{
		/*if (moves.size() > 0) //todo ver com o Igor
		 {
		 for (unsigned int i = 0; i < moves.size(); i++)
		 delete moves[i];
		 moves.clear();
		 }*/
	}

	void first()
	{
		for (int r1 = 0; r1 < r.size(); r1++)
		{
			for (int r2 = 0; r2 < r.size(); r2++)
			{
				if (r1 != r2)
				{
					for (int cli = 0; cli < ((int) r.at(r1).size()) - 1; cli++)
					{
						for (int pos = 0; pos <= r.at(r2).size(); pos++)
						{
							moves.push_back(new MoveVRPShift20(pPRVP, r1, r2, cli, pos));
						}
					}
				}
			}
		}
		if (moves.size() > 0)
		{
			m = moves[index];
		}
		else
			m = NULL;
	}

	void next()
	{
		index++;
		if (index < moves.size())
		{
			m = moves[index];
		}
		else
			m = NULL;
	}

	bool isDone()
	{
		return m == NULL;
	}

	MoveVRPShift20& current()
	{
		if (isDone())
		{
			cout << "There isnt any current element!" << endl;
			cout << "NSSeqVRPShift20. Aborting." << endl;
			exit(1);
		}

		return *m;
	}
};

class NSSeqVRPShift20: public NSSeq<RepPRV, MemPRVP>
{
private:

	PRVPProblemInstance& pPRVP;

public:

	NSSeqVRPShift20(PRVPProblemInstance& _pPRVP) :
		pPRVP(_pPRVP)
	{
	}

	virtual ~NSSeqVRPShift20()
	{
	}

	virtual Move<RepPRV, MemPRVP>& move(const RepPRV& rep)
	{
		if (rep.size() < 2)
			return *new MoveVRPShift20(pPRVP, -1, -1, -1, -1);

		int r1 = rand() % rep.size();
		if (rep.at(r1).size() < 2)
		{
			return *new MoveVRPShift20(pPRVP, -1, -1, -1, -1);
		}

		int r2;
		do
		{
			r2 = rand() % rep.size();
		} while (r1 == r2);

		int cli = rand() % (rep.at(r1).size() - 1);

		int pos = rand() % (rep.at(r2).size() + 1);
		return *new MoveVRPShift20(pPRVP, r1, r2, cli, pos); // return a random move
	}

	virtual NSIteratorVRPShift20& getIterator(const RepPRV& r)
	{
		return *new NSIteratorVRPShift20(pPRVP, r);
	}

	virtual void print()
	{
		cout << "NSSeqVRPShift20" << endl;
	}
};

#endif /*NSSeqVRPShift20_HPP_*/

